using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;

using Atomic.Libraries.Mathematics;
using Atomic.Libraries.Plotting.SpacePlotting;
using Atomic.Structures.Symmetries;

namespace Atomic.Structures
{
	[Serializable]
	[DebuggerDisplay("{DebuggerDisplay}")]
	public class SimpleCubicLattice : Lattice
	{
		public SimpleCubicLattice(double a)
			: this(Basis.StandardBasis, a, SpaceMatrix.Identity)
		{
		}

		public SimpleCubicLattice(Basis basis, double a, SpaceMatrix transform)
			: this(basis.Decompose(new Position(transform * new SpaceVector(1.0, 0.0, 0.0)) * a), basis.Decompose(new Position(transform * new SpaceVector(0.0, 1.0, 0.0))) * a, basis.Decompose(new Position(transform * new SpaceVector(0.0, 0.0, 1.0))) * a, a, transform)
		{
		}

		private SimpleCubicLattice(Position a, Position b, Position c, double a0, SpaceMatrix transform)
			: base(a, b, c)
		{
			if (a0 <= 0.0)
			{
				throw new ArgumentException();
			}

			CheckOrthogonal(transform);

			A0 = a0;
			Transform = transform;

			Gamma = new Position(this, SymmetryPoints.Gamma);
			R = new Position(this, SymmetryPoints.R);
			X = new Position(this, SymmetryPoints.X);
			M = new Position(this, SymmetryPoints.M);
		}

		public static bool TryParse(Basis basis, SpaceVector a, SpaceVector b, SpaceVector c, out Lattice lattice)
		{
			// Chosen to be somewhat larger than VASP's output precision.
			double tol = 1.0e-7;

			// The reference lattice built directly from the vectors.
			Lattice lattice0 = new Lattice(basis, a, b, c);

			// Try creating a simple cubic lattice. The lattice constant of any simple cubic basis should have this relation to the norm.
			double a0 = SpaceMatrix.Norm(lattice0.PhysicalMap) / Math.Sqrt(3.0);

			if (a0 <= tol)
			{
				// Far too small lattice constant. Give up trying to parse.
				lattice = null;
				return false;
			}

			lattice = new FaceCenteredCubicLattice(basis, a0, SpaceMatrix.Identity);
			if (SpaceMatrix.Norm(lattice0.PhysicalMap - lattice.PhysicalMap) < tol)
			{
				// The mapping is identical. Parsed successfully.
				return true;
			}

			return false;
		}

		protected override Lattice CreateReciprocalLattice()
		{
			return new SimpleCubicLattice(Basis.StandardBasis, 2.0 * Math.PI / A0, Transform);
		}

		public IEnumerable<ISpacePlot> PlotWignerSeitzCell()
		{
			throw new NotImplementedException();
			/*// Point marking two of the boundary lines of the Wigner-Seitz cell. By symmetry this is sufficient to draw the entire cell.
			Position p1 = new Position(this, R.Coordinates);
			Position p2 = new Position(this, M.Coordinates);

			// Needs an atom a the origin to determine symmetries; so just add an arbitrary atom.
			foreach (SpaceGroupSymmetryOperation o in new Structure(this).Add(Atom.Vacancy, SpaceVector.Zero).SpaceGroup.SymmetryOperations)
			{
				Position q1 = o.Transform(p1);
				Position q2 = o.Transform(p2);
				yield return new SpaceLine(q1.PhysicalCoordinates, q2.PhysicalCoordinates);
			}

			// Plot symmetry lines (i.e. boundary of the IBZ).
			yield return new SpaceLine(Gamma.PhysicalCoordinates, R.PhysicalCoordinates);
			yield return new SpaceLine(Gamma.PhysicalCoordinates, X.PhysicalCoordinates);
			yield return new SpaceLine(Gamma.PhysicalCoordinates, M.PhysicalCoordinates);
			yield return new SpaceLine(R.PhysicalCoordinates, X.PhysicalCoordinates);
			yield return new SpaceLine(R.PhysicalCoordinates, M.PhysicalCoordinates);
			yield return new SpaceLine(X.PhysicalCoordinates, M.PhysicalCoordinates);*/
		}

		/// <summary>
		/// Lattice constant.
		/// </summary>
		public double A0
		{
			get;
			private set;
		}

		/// <summary>
		/// The orthogonal transform matrix from the standard lattice vector representation.
		/// </summary>
		public SpaceMatrix Transform
		{
			get;
			private set;
		}

		public Position Gamma
		{
			get;
			private set;
		}

		public Position R
		{
			get;
			private set;
		}

		public Position X
		{
			get;
			private set;
		}

		public Position M
		{
			get;
			private set;
		}

		private string DebuggerDisplay
		{
			get
			{
				return string.Format(CultureInfo.InvariantCulture, "sc, A = ({0:f4}, {1:f4}, {2:f4}), B = ({3:f4}, {4:f4}, {5:f4}), C = ({6:f4}, {7:f4}, {8:f4})",
					A.PhysicalCoordinates.X, A.PhysicalCoordinates.Y, A.PhysicalCoordinates.Z, 
					B.PhysicalCoordinates.X, B.PhysicalCoordinates.Y, B.PhysicalCoordinates.Z, 
					C.PhysicalCoordinates.X, C.PhysicalCoordinates.Y, C.PhysicalCoordinates.Z);
			}
		}

		public static class SymmetryPoints
		{
			static SymmetryPoints()
			{
				Gamma = new SpaceVector(0.0, 0.0, 0.0);
				R = new SpaceVector(0.5, 0.5, 0.5);
				X = new SpaceVector(0.0, 0.5, 0.0);
				M = new SpaceVector(0.5, 0.5, 0.0);
			}

			public static SpaceVector Gamma
			{
				get;
				private set;
			}

			public static SpaceVector R
			{
				get;
				private set;
			}

			public static SpaceVector X
			{
				get;
				private set;
			}

			public static SpaceVector M
			{
				get;
				private set;
			}
		}
	}
}
